﻿using UnityEngine;
using System.Collections.Generic;
using System.Collections;
using TsiU;
using System.IO;
using UnityEngine.SceneManagement;
using System.Reflection;
using System;
//using COW;
using LitJson;
#if UNITY_EDITOR
using UnityEditor;
#endif
//using ResourceQuality = COW.GamePlay.EQuality;

namespace GCommon
{
    public partial class ResourceManager : TSingleton<ResourceManager>
    {
        public enum EResourceConfigLevelSetting : byte
        {
            None, Replace, Hide
        }
        public enum ELoadAbOption : byte
        {
            NotAb, AbWithName, AbWithPath, AbWithNameOrPath
        }

        public class ResourceConfigAB
        {
            //quality level: m_pathLow < m_path < m_pathHigh
            public string m_path;
            public EResourceConfigLevelSetting m_LevelLow;
            public EResourceConfigLevelSetting m_LevelHigh;
            public ELoadAbOption m_AbOption;
            public string m_abPath;
            public MethodInfo m_Modifier;
            //public bool HaveFolder = false;
            public bool IsAb { get { return m_AbOption != ELoadAbOption.NotAb; } }
            //public string GetPath(ResourceQuality s)
            //{
            //    string strResPath = m_path;
            //    switch (s)
            //    {
            //        case ResourceQuality.Lowest:
            //            {
            //                if (m_LevelLow == EResourceConfigLevelSetting.Hide)
            //                    return null;
            //                else if (m_LevelLow == EResourceConfigLevelSetting.Replace)
            //                    strResPath += "_Low";
            //            }
            //            break;
            //        case ResourceQuality.Low:
            //            break;
            //        case ResourceQuality.High:
            //        case ResourceQuality.Ultra:
            //            {
            //                if (m_LevelHigh == EResourceConfigLevelSetting.Hide)
            //                    return null;
            //                else if (m_LevelHigh == EResourceConfigLevelSetting.Replace)
            //                    strResPath += "_High";
            //            }
            //            break;
            //    }
            //    if (m_Modifier != null)
            //    {
            //        string[] arg = { strResPath };
            //        strResPath = (string)m_Modifier.Invoke(null, arg);
            //    }
            //    return strResPath;
            //}
        }

        public class ResourceConfigStreamAB : ResourceConfigAB
        {
            public string m_streamABPath;
        }
        const int ResourceSize = 24100;
        const int ABSize = 10000;
        const int ResourceAnoymous = 15000; 
        public const string ResconfPath = "Config/Resconf";
#if FULL_HD
        public const string ResconfPathHD = "HD/Config/Resconf";
        public const string WwiseResconfPathHD = "HD/Config/Resconf_Wwise";
#endif

#if SPECTATOR_MODE
        public const string ResconfPathPC = "PC_Resource/Config/Resconf";
#endif

        // Resource ID ==> Resource path
        private Dictionary<ResourceID, string> m_ResourcePaths = new Dictionary<ResourceID, string>(ResourceSize);
        private Dictionary<ResourceID, ResourceConfigAB> m_ResourceMappingAB = new Dictionary<ResourceID, ResourceConfigAB>(ABSize);

        // Resource ID Name ==> Resource ID
        private Dictionary<string, ResourceID> m_ResourceAnoymousMapping = new Dictionary<string, ResourceID>(ResourceAnoymous);
        private Dictionary<string, ResourceID> m_ResourceAnoymousMappingTmp = new Dictionary<string, ResourceID>(ResourceAnoymous);
        // Resource ID ==> Resource name string
        private Dictionary<ResourceID, string> m_NameMapping = new Dictionary<ResourceID, string>(5800);

        // ab name => dependent ab names (defined in Resconf.json for optional bundles)
        private Dictionary<string, List<string>> m_ResconfDependencies = new Dictionary<string, List<string>>();

        private Dictionary<Enum, ResourceID[]> m_PreloadResource = new Dictionary<Enum, ResourceID[]>();
        private ResourceID m_ResourceStartID = 0;
        private bool m_HasInited = false;
        private bool m_HasParsed = false;
        private AssetBundleManifest m_ABManifest;
        private bool m_UseAssetBundle;

        private Dictionary<string, UnityEngine.Object> m_LoadedResources = new Dictionary<string, UnityEngine.Object>();
        private Dictionary<string, AssetBundle> m_ResourceBundle = new Dictionary<string, AssetBundle>();

        private Dictionary<string, UnityEngine.Object> m_DontDestroyLoadedResources = new Dictionary<string, UnityEngine.Object>();
        private Dictionary<string, AssetBundle> m_DontDestroyResourceBundle = new Dictionary<string, AssetBundle>();

        private Dictionary<string, UnityEngine.Object> m_PreLoadedResources = new Dictionary<string, UnityEngine.Object>();
        private Dictionary<string, AssetBundle> m_PreLoadedResourceBundles = new Dictionary<string, AssetBundle>();

        string[] mPersisitResources = null;

        //set on launch and setting change
        //private ResourceQuality ResQualityLevel = ResourceQuality.Low;

        public static bool UseSceneFolderAB = true;

        private Type mResIDType;
        private Dictionary<string, FieldInfo> mResIDFieldInfos;

        //public void SetResQualityLevel(ResourceQuality q)
        //{
        //    ResQualityLevel = q;
        //}

        /*string GetResPathByQualityLevel(ResourceConfig c)
        {
            return c.GetPath(ResQualityLevel);
        }*/

        public bool Init(bool useAssetBundle, string[] dontDestoryResources = null)
        {
            if (m_HasInited)
            {
                return true;
            }
            m_UseAssetBundle = useAssetBundle;
            m_ResourceBundle.Clear();
            m_LoadedResources.Clear();
            m_DontDestroyLoadedResources.Clear();
            m_DontDestroyResourceBundle.Clear();
            if (m_UseAssetBundle)
            {
                return false;


                //AssetBundle ab = ResHotUpdater.instance.LoadAssetBundle(ResHotUpdater.instance.AssetBundleManifestPath);

                //if (ab == null)
                //{
                //    return false;
                //}

                //m_ABManifest = ab.LoadAsset("AssetBundleManifest") as AssetBundleManifest;

                //ab.Unload(false);
                //if (m_ABManifest != null)
                //{
                //    m_HasInited = true;
                //}
            }
            else
            {
                m_HasInited = true;
            }
            mPersisitResources = dontDestoryResources;
            //MemoryProfiler.BeginMonoProfile("Resourcemanager Persisit Resource");
            LoadPersisitResource();
            //MemoryProfiler.EndMonoProfile("Resourcemanager Persisit Resource");
            return m_HasInited;
        }

        public bool Inited { get { return m_HasInited; } }
        public void Cleanup()
        {
            if (m_HasInited == false)
            {
                return;
            }
            m_DontDestroyLoadedResources.Clear();
            foreach (KeyValuePair<string, AssetBundle> pair in m_DontDestroyResourceBundle)
            {
                pair.Value.Unload(true);
            }
            m_DontDestroyResourceBundle.Clear();
            ClearAllLoadedResourceInternal(true);
            //CleanUpAsyncLoadResRequests();
            m_HasInited = false;
        }
        public void LoadPersisitResource()
        {
            if (m_HasInited == true && mPersisitResources != null)
            {
                //load dont destory resource
                foreach (string resPath in mPersisitResources)
                {
                    LoadDontDestoryResource(resPath);
                }
            }
        }

        private bool ResourceContains(ResourceID resourceID)
        {
            return m_ResourcePaths.ContainsKey(resourceID) || m_ResourceMappingAB.ContainsKey(resourceID);
        }

        public bool AddResourceMapping(string resPath, ResourceID resourceID, string abPath = "", string streamABPath = "", string strName = "", ELoadAbOption isAB = ELoadAbOption.NotAb,
            EResourceConfigLevelSetting levelLow = EResourceConfigLevelSetting.None,
            EResourceConfigLevelSetting levelHigh = EResourceConfigLevelSetting.None, MethodInfo modifier = null, bool needNameHash = true, bool overwrite = false)
        {
            if (ResourceContains(resourceID) && !overwrite)
            {
                return false;
            }

            if (string.IsNullOrEmpty(abPath) && isAB == ELoadAbOption.NotAb
                && levelLow == EResourceConfigLevelSetting.None && levelHigh == EResourceConfigLevelSetting.None
                && modifier == null)
            {
                m_ResourcePaths[resourceID] = resPath;
            }
            else
            {
                ResourceConfigAB resConfig = null;
                if (string.IsNullOrEmpty(streamABPath))
                {
                    resConfig = new ResourceConfigAB();
                }
                else
                {
                    resConfig = new ResourceConfigStreamAB();
                    (resConfig as ResourceConfigStreamAB).m_streamABPath = streamABPath;
                }
                resConfig.m_path = resPath;
                resConfig.m_LevelLow = levelLow;
                resConfig.m_LevelHigh = levelHigh;
                resConfig.m_AbOption = isAB;
                resConfig.m_abPath = abPath;
                resConfig.m_Modifier = modifier;
                if (modifier != null && isAB == ELoadAbOption.AbWithPath)
                {
                    Debug.LogError("modifier while AbWithPath");
                }
                m_ResourceMappingAB[resourceID] = resConfig;
            }


            if (needNameHash && strName != "" && !m_NameMapping.ContainsKey(resourceID))
            {
                m_NameMapping.Add(resourceID, strName);
            }

            if (resourceID == m_ResourceStartID)
                m_ResourceStartID += 1;

            return true;
        }

        public bool AddResourceAnoymousMapping(string resName, bool anoymous, ResourceID resourceID)
        {
            if (!m_ResourceAnoymousMapping.ContainsKey(resName))
            {
                m_ResourceAnoymousMapping.Add(resName, resourceID);
                if (anoymous)
                    m_ResourceAnoymousMappingTmp.Add(resName, resourceID);
            }
            return true;
        }

        public void SwapAndReleaseAnoymousMappingTmp()
        {
            m_ResourceAnoymousMapping = m_ResourceAnoymousMappingTmp;
            m_ResourceAnoymousMappingTmp = new Dictionary<string, ResourceID>();
        }

        //IMPORTANT
        //Call this function only in Init
        private void LoadDontDestoryResource(string resPath)
        {
            resPath = resPath.ToLower();
#if RESLOAD_TRACK
            if (resPath != string.Empty)
            {
                var stats = GetResLoadStats(resPath);
                stats.TimesFromPath++;
            }
#endif
            if (m_DontDestroyLoadedResources.ContainsKey(resPath) && m_DontDestroyLoadedResources[resPath] != null)
            {
                //loaded already
                return;
            }
            //preload
            GetResourceInternal(resPath, ELoadAbOption.AbWithNameOrPath, null, true);
        }

        //public UnityEngine.Object PoolGetRes(ResourceID id, Transform tranformParent = null)
        //{
        //    return PoolManager.GetRes(id, tranformParent);
        //}
        //public bool PoolReturnRes(ResourceID id, UnityEngine.Object obj, bool worldPositionStays = true)
        //{
        //    return PoolManager.RecyleRes(id, obj, worldPositionStays);
        //}
        public string GetResourceAbName(ResourceID id)
        {
            ResourceConfigAB resConfig;
            if (m_ResourceMappingAB.TryGetValue(id, out resConfig))
            {
                if (!resConfig.IsAb)
                    return null;
                return resConfig.m_abPath;
            }
            return null;
        }

        public string GetResourceStreamAbName(ResourceID id)
        {
            ResourceConfigAB resConfig;
            if (m_ResourceMappingAB.TryGetValue(id, out resConfig))
            {
                if (!resConfig.IsAb)
                    return null;
                if(resConfig is ResourceConfigStreamAB)
                {
                    var stream_config = resConfig as ResourceConfigStreamAB;
                    if(stream_config!= null)
                    {
                        return stream_config.m_streamABPath;
                    }
                }
                return null;
            }
            return null;
        }

#if RESLOAD_TRACK
        public class ResLoadStats
        {
            public string ResPath = string.Empty;
            public bool FromAB = false;
            public bool FromRes = false;

            public float TimeStartLoading = 0f;
            public float TimeEndLoading = 0f;
            public float Cost { get { return TimeEndLoading - TimeStartLoading; } }

            public int TimesFromID = 0;
            public int TimesFromPath = 0;
            public int TimesFromPool = 0;
        }

        public bool IsResLoadFromPoolMgr = false;

        private Dictionary<string, ResLoadStats> mDictResLoadStats = new Dictionary<string, ResLoadStats>();

        public ResLoadStats GetResLoadStats(string resPath)
        {
            var path = resPath.ToLower();
            ResLoadStats stats = null;
            if (mDictResLoadStats.TryGetValue(path, out stats))
            {
                return stats;
            }
            else
            {
                stats = new ResLoadStats();
                stats.ResPath = path;
                mDictResLoadStats[path] = stats;
                return stats;
            }
        }

        public string GetPathWithQuality(ResourceID resourceID)
        {
            if (m_ResourcePaths.ContainsKey(resourceID))
            {
                return m_ResourcePaths[resourceID];
            }
            else if (m_ResourceMappingAB.ContainsKey(resourceID))
            {
                return m_ResourceMappingAB[resourceID].GetPath(ResQualityLevel);
            }
            return null;
        }
#endif

        public UnityEngine.Object GetResource(ResourceID id, bool notUsingABForcefully = false, Type type = null, bool noCache = false)
        {
            if (m_ResourcePaths.ContainsKey(id))
            {
#if RESLOAD_TRACK
                if (IsResLoadFromPoolMgr)
                    IsResLoadFromPoolMgr = false;
                else
                {
                    var stats = GetResLoadStats(m_ResourcePaths[id]);
                    stats.TimesFromID++;
                    stats.FromRes = true;
                }
#endif
                return GetResource(m_ResourcePaths[id], ELoadAbOption.NotAb, string.Empty, type, noCache);
            }
            else if (m_ResourceMappingAB.ContainsKey(id))
            {
                ResourceConfigAB resConfig = m_ResourceMappingAB[id];
                //notUsingABForcefully = (notUsingABForcefully ? notUsingABForcefully : !resConfig.m_AbOption);
                var abOpt = notUsingABForcefully ? ELoadAbOption.NotAb : resConfig.m_AbOption;

                return null;
                //string strResPath = resConfig.GetPath(ResQualityLevel);
                //if (strResPath == null)
                //{
                //    return null;
                //}

#if RESLOAD_TRACK
                if (IsResLoadFromPoolMgr)
                    IsResLoadFromPoolMgr = false;
                else
                {
                    var stats = GetResLoadStats(strResPath);
                    stats.TimesFromID++;
                    stats.FromAB = true;
                }
#endif
                //var t = GetResource(strResPath, abOpt, resConfig.m_abPath, type, noCache);
                //return t;
            }
#if !LIVE_BUILD
            Debug.LogError("no resource mapping found: " + id);
#endif
            return null;
        }
        public UnityEngine.Object GetResource(string resPath, bool notUsingABForcefully = false, bool noCache = false)
        {
#if RESLOAD_TRACK
            var stats = GetResLoadStats(resPath);
            stats.TimesFromPath++;
#endif
            var ab = notUsingABForcefully ? ELoadAbOption.NotAb : ELoadAbOption.AbWithNameOrPath;
            return GetResource(resPath, ab, "", null, noCache);
        }
        UnityEngine.Object GetResource(string resPath, ELoadAbOption abOpt, string ab = "", Type type = null, bool noCache = false)
        {
            resPath = resPath.ToLower();
            if (m_LoadedResources.ContainsKey(resPath))
            {
                var resObj = m_LoadedResources[resPath];
                if (resObj != null)
                {
                    return m_LoadedResources[resPath];
                }
            }
            if (m_DontDestroyLoadedResources.ContainsKey(resPath))
            {
                var resObj = m_DontDestroyLoadedResources[resPath];
                if (resObj != null)
                {
                    return m_DontDestroyLoadedResources[resPath];
                }
            }
            var res = GetResourceInternal(resPath, abOpt, ab, false, type, noCache);
            return res;
        }


        private UnityEngine.Object GetResourceInternalFull(string resPath, bool notUsingABForcefully = false, bool dontDestroy = false, bool isDependency = false)
        {
            if (m_UseAssetBundle && notUsingABForcefully == false)
            {
                string[] dependency = m_ABManifest.GetAllDependencies(resPath);

                for (int i = 0; i < dependency.Length; i++)
                {
                    string dependencyResName = dependency[i];
                    GetResourceInternalFull(dependencyResName, notUsingABForcefully, dontDestroy, true);
                }

                AssetBundle ab = LoadABWithoutLoadingAssets(resPath, dontDestroy);
                if (ab == null)
                {
                    Debug.LogError("load resource failed 1: null ab " + resPath);
                    return null;
                }

                if (ab.isStreamedSceneAssetBundle == false)
                {
                    string resName = Path.GetFileNameWithoutExtension(resPath);

                    if (isDependency)
                    {
#if DEVELOPMENT_BUILD
                        UnityEngine.Profiling.Profiler.BeginSample("Resources.ab.LoadAllAssets_" + resPath);
                        Debug.Log("[ResourceManager]ab.LoadAllAssets " + resPath);
#endif
                        ab.LoadAllAssets();
#if DEVELOPMENT_BUILD
                        UnityEngine.Profiling.Profiler.EndSample();
#endif
                        return null;
                    }
                    else
                    {
#if DEVELOPMENT_BUILD
                        UnityEngine.Profiling.Profiler.BeginSample("Resources.ab.LoadAsset_" + resName);
                        Debug.Log("[ResourceManager]ab.LoadAsset " + resName);
#endif
                        UnityEngine.Object obj = ab.LoadAsset(resName);
#if DEVELOPMENT_BUILD
                        UnityEngine.Profiling.Profiler.EndSample();
#endif
                        if (obj == null)
                        {
                            Debug.LogError("load resource failed 2: null obj " + resPath);
                            return null;
                        }
                        //FinishedLoadingResource(resPath);
                        if (dontDestroy)
                        {
                            m_DontDestroyLoadedResources[resPath] = obj;
                        }
                        else
                        {
                            m_LoadedResources[resPath] = obj;
                        }
                        return obj;
                    }
                }
                else
                {
                    Debug.LogError("not a valid resource" + resPath);
                    return null;
                }
            }
            else
            {
#if DEVELOPMENT_BUILD
                UnityEngine.Profiling.Profiler.BeginSample("Resources.Load_" + resPath);
                Debug.Log("[ResourceManager]Resources.Load " + resPath);
#endif
                UnityEngine.Object obj = Resources.Load(resPath);
#if DEVELOPMENT_BUILD
                UnityEngine.Profiling.Profiler.EndSample();
#endif
                if (obj == null)
                {
                    Debug.LogError("load resource failed 3: null obj " + resPath);
                    return null;
                }
                if (dontDestroy)
                {
                    m_DontDestroyLoadedResources[resPath] = obj;
                }
                else
                {
                    m_LoadedResources[resPath] = obj;
                }
                return obj;
            }
        }


        private UnityEngine.Object GetResourceInternal(string resPath,
            ELoadAbOption abOpt = ELoadAbOption.NotAb, string abPath = "",
            bool dontDestroy = false, Type type = null, bool noCache = false)
        {
            UnityEngine.Object obj;

            if (m_UseAssetBundle && abOpt != ELoadAbOption.NotAb)
            {
#if RESLOAD_TRACK
                var stats = GetResLoadStats(resPath);
                stats.FromAB = true;
                stats.TimeStartLoading = Time.realtimeSinceStartup;
#endif
                if (string.IsNullOrEmpty(abPath))
                    abPath = resPath;
                else
                {
                    if (!LoadResconfDependenciesInNeed(abPath))
                    {
                        Debug.LogError("load Resconf dependency failed: " + abPath);
                        return null;
                    }
                }

                string[] dependency = m_ABManifest.GetAllDependencies(abPath);

                for (int i = 0; i < dependency.Length; i++)
                {
                    string dependencyResName = dependency[i];

                    if (LoadABWithoutLoadingAssets(dependencyResName, dontDestroy) == null)
                    {
                        Debug.LogError("load dependency resource failed: " + dependencyResName);
                        return null;
                    }
                }

                AssetBundle ab = LoadABWithoutLoadingAssets(abPath, dontDestroy);
                if (ab == null)
                {
#if !LIVE_BUILD
                    Debug.LogError("load resource failed 4: null ab " + abPath);
#endif
                    return null;
                }

                if (ab.isStreamedSceneAssetBundle == false)
                {
                    var byPath = false;
                    switch (abOpt)
                    {
                        case ELoadAbOption.AbWithPath:
                            byPath = true;
                            break;
                        case ELoadAbOption.AbWithName:
                            byPath = false;
                            break;
                        case ELoadAbOption.AbWithNameOrPath:
                        default:
                            byPath = IsPathWithSuffix(resPath);
                            break;
                    }
                    string resName = byPath ? resPath : Path.GetFileNameWithoutExtension(resPath);
#if DEVELOPMENT_BUILD
                    UnityEngine.Profiling.Profiler.BeginSample("Resources.ab.LoadAsset_" + resName);
                    Debug.Log("[ResourceManager]ab.LoadAsset " + resName);
#endif
                    obj = ab.LoadAsset(resName);
                    //GameFacade.ResourceLoadCount++;

#if RESLOAD_TRACK
                    stats.TimeEndLoading = Time.realtimeSinceStartup;
#endif

#if DEVELOPMENT_BUILD
                    UnityEngine.Profiling.Profiler.EndSample();
#endif
                    //FinishedLoadingResource(resPath);
                }
                else
                {
                    Debug.LogError("not a valid resource" + resPath);
                    return null;
                }
            }
            else
            {
#if DEVELOPMENT_BUILD
                UnityEngine.Profiling.Profiler.BeginSample("Resources.Load_" + resPath);
                Debug.Log("[ResourceManager]Resources.Load " + resPath);
#endif

#if RESLOAD_TRACK
                var stats = GetResLoadStats(resPath);
                stats.FromRes = true;
                stats.TimeStartLoading = Time.realtimeSinceStartup;
#endif

                obj = type == null ? Resources.Load(resPath)
                    : Resources.Load(resPath, type);

                //GameFacade.ResourceLoadCount++;

#if RESLOAD_TRACK
                stats.TimeEndLoading = Time.realtimeSinceStartup;
#endif

#if DEVELOPMENT_BUILD
                UnityEngine.Profiling.Profiler.EndSample();
#endif
            }

            if (obj == null)
            {
                Debug.LogError("load resource failed 6: null obj " + resPath);
            }
            else if (noCache)
            {
                //skip
            }
            else if (dontDestroy)
            {
                m_DontDestroyLoadedResources[resPath] = obj;
            }
            else
            {
                m_LoadedResources[resPath] = obj;
            }
            return obj;
        }


        public AssetBundle LoadABWithoutLoadingAssets(string resPath, bool dontDestroy)
        {
            resPath = resPath.ToLower();
            AssetBundle ab;
            if ((dontDestroy == true || m_ResourceBundle.TryGetValue(resPath, out ab) == false) && m_DontDestroyResourceBundle.TryGetValue(resPath, out ab) == false)
            {
#if DEVELOPMENT_BUILD
                UnityEngine.Profiling.Profiler.BeginSample("Resources.LoadAssetBundle_" + resPath);
                Debug.Log("[ResourceManager]LoadAssetBundle " + resPath);
#endif
                //ab = ResHotUpdater.instance.LoadAssetBundle(resPath);
#if DEVELOPMENT_BUILD
                UnityEngine.Profiling.Profiler.EndSample();
#endif




                return null;
                if (ab == null)
                {
#if !LIVE_BUILD
                    Debug.LogError("load resource failed 7: null ab " + resPath);
#endif
                    return null;
                }
                //Debug.Log("load ab " + resPath, LogColor.Yellow);
                if (dontDestroy)
                {
                    m_DontDestroyResourceBundle.Add(resPath, ab);
                }
                else
                {
                    m_ResourceBundle.Add(resPath, ab);
                }
            }
            return ab;
        }

        private AssetBundle LoadAssetBundleFromCache(string abPath, bool dontDestroy = false)
        {
            AssetBundle ab;
            abPath = abPath.ToLower();
            if ((m_ResourceBundle.TryGetValue(abPath, out ab) == false) && m_DontDestroyResourceBundle.TryGetValue(abPath, out ab) == false)
            {
                ab = null;
            }
            return ab;
        }

        public void RemoveSceneAB(string abPath)
        {
            AssetBundle ab;
            abPath = abPath.ToLower();
            if (m_ResourceBundle.ContainsKey(abPath))
            {
                ab = m_ResourceBundle[abPath];
                ab.Unload(false);
                m_ResourceBundle.Remove(abPath);
                Debug.Log("RemoveSceneAB: " + abPath);
            }
        }
        public void OnLoadSceneFinish(string scenePath, bool useAB)
        {
            if (useAB)
            {
                if (!UseSceneFolderAB)
                {
                    RemoveSceneAB(scenePath);
                }
            }
        }

        public AsyncOperation StartLoadingSceneAsync(string scenePath, string abPath = "", LoadSceneMode mode = LoadSceneMode.Single, bool notUsingABForcefully = false)
        {
            bool isSubScene = false;

            if (abPath == "")
            {
                scenePath = scenePath.ToLower();
                abPath = scenePath;
            }
            else
            {
                // isSubScene = true;
                scenePath = scenePath.ToLower();
                abPath = abPath.ToLower();
            }
            bool useAB = m_UseAssetBundle && notUsingABForcefully == false;
            string sceneName = scenePath;
            if (useAB)
            {
                AssetBundle ab = LoadAssetBundleFromCache(abPath, false);
                if (ab == null)
                {
                    string[] dependency = m_ABManifest.GetAllDependencies(abPath);
                    for (int i = 0; i < dependency.Length; i++)
                    {
                        string dependencyResName = dependency[i];
                        if (LoadABWithoutLoadingAssets(dependencyResName, false) == null)
                        {
                            Debug.LogError("load dependency resource failed: " + dependencyResName);
                            return null;
                        }
                    }
                    ab = LoadABWithoutLoadingAssets(abPath, false);
                }

                if (ab == null)
                {
                    Debug.LogError("load resource failed 8: null ab " + abPath);
                    return null;
                }
                if (ab.isStreamedSceneAssetBundle == true)
                {
                    if (!isSubScene)
                    {
                        string[] scenePaths = ab.GetAllScenePaths();
                        if (scenePaths == null || scenePaths.Length == 0)
                        {
                            return null;
                        }
                        //Debug.Log("load Scene: " + scenePath + "scenePaths = " + scenePaths[0]);
                        sceneName = Path.GetFileNameWithoutExtension(scenePaths[0]);
                    }
                }
                else
                {
                    return null;
                }
            }
#if UNITY_EDITOR
            if (!useAB)
            {
                bool found = false;
                foreach (var scene in EditorBuildSettings.scenes)
                {
                    if (scene.path.ToLower().Contains(sceneName))
                    {
                        found = true;
                        break;
                    }
                }
                if (!found)
                    return null;
            }
#endif
            return SceneManager.LoadSceneAsync(sceneName, mode);
        }

        public static string GetStreamABPathByABPath(string ab_path)
        {
            return ab_path + "_folder";
        }

        public string GetSceneStreamABPath(ResourceID id)
        {
            if (UseSceneFolderAB)
            {
                var streamABPath = "";
                string mainSceneABPath = "";
                bool useAB = IsResourceUseStreamAB(id, ref mainSceneABPath, ref streamABPath);
                if (useAB)
                {
                    Debug.LogError("mainSceneABPath " + mainSceneABPath + "StreamingPath " + streamABPath);
                }

                return streamABPath;
            }
            return "";
        }

        public bool IsResourceUseAB(ResourceID id, ref string abPath)
        {
            if (!ResourceContains(id))
            {
                return false;
            }

            if (m_ResourcePaths.ContainsKey(id))
            {
                abPath = m_ResourcePaths[id];
                return false;
            }
            else
            {
                var resConfig = m_ResourceMappingAB[id];
                abPath = (resConfig.m_abPath == "" ? resConfig.m_path : resConfig.m_abPath);
                return resConfig.IsAb && (m_UseAssetBundle);
            }
        }

        public bool IsResourceUseStreamAB(ResourceID id, ref string abPath, ref string streamABPath)
        {
            if (!ResourceContains(id))
            {
                return false;
            }
            if (m_ResourcePaths.ContainsKey(id))
            {
                abPath = m_ResourcePaths[id];
                streamABPath = "";
                return false;
            }
            else
            {
                var resConfig = m_ResourceMappingAB[id];
                if (resConfig is ResourceConfigStreamAB)
                {
                    var stream_config = resConfig as ResourceConfigStreamAB;
                    abPath = (stream_config.m_abPath == "" ? stream_config.m_path : stream_config.m_abPath);
                    streamABPath = stream_config.m_streamABPath;
                    return stream_config.IsAb && string.IsNullOrEmpty(streamABPath) == false && m_UseAssetBundle;
                }
                else
                {
                    abPath = (resConfig.m_abPath == "" ? resConfig.m_path : resConfig.m_abPath);
                    streamABPath = "";
                    return false;
                }
            }
        }

        public void FinishLoadingScene(ResourceID id)
        {
            ResourceConfigAB resConfig;
            if (m_ResourceMappingAB.TryGetValue(id, out resConfig) == false)
            {
                return;
            }

            //if (resConfig.IsAb)
            //{
            //    string strResPath = resConfig.GetPath(ResQualityLevel);
            //    if (strResPath == null)
            //    {
            //        return;
            //    }
            //    string abPath = resConfig.m_abPath;
            //    if (abPath == "")
            //    {
            //        strResPath = strResPath.ToLower();
            //        abPath = strResPath;
            //    }
            //    else
            //    {
            //        abPath = abPath.ToLower();
            //    }
            //    RemoveSceneAB(abPath);
            //}
        }

        public AsyncOperation StartLoadingSceneAsync(ResourceID id, LoadSceneMode mode = LoadSceneMode.Single, bool notUsingABForcefully = false, bool need_modify = false)
        {
            if (!ResourceContains(id))
            {
                return null;
            }

            if (m_ResourcePaths.ContainsKey(id))
            {
                return StartLoadingSceneAsync(m_ResourcePaths[id], string.Empty, mode, true);
            }
            else
            {
                ResourceConfigAB resConfig = m_ResourceMappingAB[id];
                notUsingABForcefully = (notUsingABForcefully ? notUsingABForcefully : !resConfig.IsAb);
                return null;
                //string strResPath = resConfig.GetPath(ResQualityLevel);
                //if (strResPath == null)
                //{
                //    return null;
                //}
                //return StartLoadingSceneAsync(strResPath, resConfig.m_abPath, mode, notUsingABForcefully);    //now using Low/High replacements
            }
        }
        public ResourceID[] GetPreloadResource(Enum gametypeormode)
        {
            ResourceID[] ret;
            if (m_PreloadResource.TryGetValue(gametypeormode, out ret))
            {
                return ret;
            }
            return null;
        }

        //public string GetPath(ResourceID resourceID, ResourceQuality rq)
        //{
        //    if (m_ResourcePaths.ContainsKey(resourceID))
        //    {
        //        return m_ResourcePaths[resourceID];
        //    }
        //    else if (m_ResourceMappingAB.ContainsKey(resourceID))
        //    {
        //        return m_ResourceMappingAB[resourceID].GetPath(rq);
        //    }
        //    return null;
        //}

        public string GetPath(ResourceID resourceID)
        {
            if(m_ResourcePaths.ContainsKey(resourceID))
            {
                return m_ResourcePaths[resourceID];
            }
            else if(m_ResourceMappingAB.ContainsKey(resourceID))
            {
                return m_ResourceMappingAB[resourceID].m_path;
            }
            return null;
        }

        // [jl] return a temp list, not good for runtime usage, only for editors/tools. 
        public List<ResourceID> GetResourceIDs()
        {
            List<ResourceID> ret = new List<ResourceID>();
            ret.AddRange(m_ResourcePaths.Keys);
            ret.AddRange(m_ResourceMappingAB.Keys);

            return ret;
        }

        public string GetResourceIDName(ResourceID ResID, bool InLogErr = true)
        {
            string strName;
            if (m_NameMapping.TryGetValue(ResID, out strName))
            {
                return strName;
            }

            if (InLogErr)
                Debug.LogError("Failed to find name of Resource  id:" + ResID.ToString());

            return "";
        }

        public ResourceID GetResourceIDByName(Type ResID, string strResid, bool InLogErr = true)
        {
            ResourceID ret = ResourceID.INVALID;

            if (string.IsNullOrEmpty(strResid))
            {
                Debug.LogWarning("@ResourceManager.GetResourceIDByName(): string.IsNullOrEmpty(strResid)");
                return ret;
            }

            // Continue to check if it's a anonymous resource ID.
            if (m_ResourceAnoymousMapping.TryGetValue(strResid, out ret))
            {
                return ret;
            }
            else
            {
                FieldInfo ResourceNameid = ResID.GetField(strResid);
                if (null == ResourceNameid)
                {
                    if (InLogErr)
                    {
                        Debug.LogError("Undefined Resource Name: " + strResid);
                    }
                    return ResourceID.INVALID;
                }
                else
                {
                    ret = (ResourceID)ResourceNameid.GetValue(ResID);
                }
            }

            if (0 == ret)
            {
                if (InLogErr)
                {
                    Debug.LogError("Unload Resource Name: " + strResid);
                }
                return ResourceID.INVALID;
            }

            return ret;
        }

        private void parseResourceTag(List<string> abs, Type ResID, JsonData dataWithTag, out string url, out string ab,out string stream_ab, out ELoadAbOption isAB, out string deps,
            out EResourceConfigLevelSetting lowSetting, out EResourceConfigLevelSetting highSetting, out MethodInfo modifier)
        {
            JsonData urlData;
            if (dataWithTag.TryGetValue("url", out urlData))
            {
                url = (string)urlData;
            }
            else
            {
                url = null;
            }
            lowSetting = EResourceConfigLevelSetting.None;
            highSetting = EResourceConfigLevelSetting.None;
            modifier = null;
            isAB = ELoadAbOption.NotAb;
            ab = "";
            deps = null;
            stream_ab = "";
            IDictionary IDataWithTag = (IDictionary)dataWithTag;
            if (IDataWithTag.Contains("low"))
            {
                bool b;
                if (bool.TryParse(dataWithTag["low"].ToString(), out b))
                {
                    lowSetting = b ? EResourceConfigLevelSetting.Replace : EResourceConfigLevelSetting.Hide;
                }
            }
            if (IDataWithTag.Contains("high"))
            {
                bool b;
                if (bool.TryParse(dataWithTag["high"].ToString(), out b))
                {
                    highSetting = b ? EResourceConfigLevelSetting.Replace : EResourceConfigLevelSetting.Hide;
                }
            }

            if (IDataWithTag.Contains("modifier"))
            {
                Type[] ps = { typeof(string) };
                modifier = ResID.GetMethod((string)dataWithTag["modifier"], ps);
            }


            if (IDataWithTag.Contains("useAB"))
            {
                /*
                var b = System.Convert.ToBoolean((int)dataWithTag["useAB"]);
                if (b)
                {
                    var withSuffix = IsPathWithSuffix(url);
                    isAB = withSuffix ? ELoadAbOption.AbWithPath : ELoadAbOption.AbWithName;
                }
                */
                int i = (int)dataWithTag["useAB"];
                isAB = (ELoadAbOption)i;
            }

            if (IDataWithTag.Contains("abIdx"))
            {
                var abIdx = (int)dataWithTag["abIdx"];
                if (abs != null && abIdx < abs.Count)    //assert abIdx >= 0
                {
                    ab = abs[abIdx];
                }
            }
            if (ab == "" && IDataWithTag.Contains("ab"))
            {
                ab = (string)dataWithTag["ab"];
            }

            if (IDataWithTag.Contains("abDep"))
            {
                deps = dataWithTag["abDep"].ToString();
            }

            if(IDataWithTag.Contains("streamAB"))
            {
                stream_ab = dataWithTag["streamAB"].ToString();
            }
        }
        static bool IsPathWithSuffix(string url)
        {
            if (string.IsNullOrEmpty(url))
                return false;
            var dirIndex = url.LastIndexOf('/');
            var dotIndex = url.LastIndexOf('.');
            var withSuffix = dirIndex > 0 && dotIndex > dirIndex;
            return withSuffix;
        }
        private void parseAndAddResourceMapping(Type ResID,
            string strName, string url, string ab, string stream_ab, ELoadAbOption isAB,
            EResourceConfigLevelSetting lowSetting, EResourceConfigLevelSetting highSetting, MethodInfo modifier, bool needNameToResID, bool overwrite)
        {
            ResourceID IDdefined = 0;
            FieldInfo ResourceNameid = GetResFieldInfoByName(strName);
            // Check if the ID is defined in code, otherwise the 'null' value will freeze the engine within following 'ResourceNameid.GetValue'...
            if (ResourceNameid != null)
            {
                IDdefined = (ResourceID)ResourceNameid.GetValue(ResID);
                if (IDdefined != 0 && !overwrite) //to detect if redefined
                {
                    Debug.LogError("Already Defined ID Name: " + strName);
                    return;
                }
            }
            else        // the strName is not defined in the ResourceIDDefs, check within the annoymous map
            {
                if (overwrite && m_ResourceAnoymousMapping.ContainsKey(strName))
                    IDdefined = m_ResourceAnoymousMapping[strName];
            }

            if (IDdefined == 0)
            {
                IDdefined = m_ResourceStartID;
            }

            if (ResourceNameid != null)
            {
                ResourceNameid.SetValue(ResID, IDdefined);
                AddResourceAnoymousMapping(strName, false, IDdefined);
            }
            else
            {
                AddResourceAnoymousMapping(strName, true, IDdefined);
            }

            AddResourceMapping(url, IDdefined, ab, stream_ab, strName, isAB, lowSetting, highSetting, modifier, needNameToResID, overwrite);
        }

        private void InitResFieldsDict(Type ResID)
        {
            mResIDType = ResID;
            var fields = mResIDType.GetFields();

            mResIDFieldInfos = new Dictionary<string, FieldInfo>(fields.Length + 20);
            for (int i = 0; i < fields.Length; i++)
            {
                var filedInfo = fields[i];
                mResIDFieldInfos[filedInfo.Name] = filedInfo;
            }
        }

        private FieldInfo GetResFieldInfoByName(string name)
        {
            if (mResIDFieldInfos.ContainsKey(name))
            {
                return mResIDFieldInfos[name];
            }
            return null;
        }

        public static List<string> parseResourceAbList(JsonData JObjRoot)
        {
            if (!JObjRoot.ContainsKey("abNames"))
                return null;
            var abJson = JObjRoot["abNames"];
            if (abJson.Count == 0)
                return null;
            var list = new List<string>();
            for(var i = 0; i < abJson.Count; i++)
            {
                var name = abJson[i].ToString();
                list.Add(name);
            }
            return list;
        }

        private bool ParseResourceConfInternal(Type ResID, Type EnumGame, Type EnumMode, string jsonPath, bool overwrite = false)
        {
            string profileTag = string.Format("ParseResource config {0}", jsonPath);
            //MemoryProfiler.BeginMonoProfile(profileTag);
            var jsonText = GetResourceNoCache(jsonPath);
            var jsonRes = jsonText.ToString();
            Debug.Log("Resource manager load json file Finish");
            JsonData JObjRoot = JsonMapper.ToObject(jsonRes);
            Debug.Log("Resource manager parseJson Finish");

            //string str = GetResource(jsonPath).ToString();
            if (null == JObjRoot)
            {
                Debug.LogError("Read Config ERROR: " + jsonPath);
                return false;
            }
            //init start id
            if (m_ResourceStartID == 0)
            {
                int tmp = (int)JObjRoot["res"]["startID"];
                if (tmp < 0)
                {
                    Debug.LogError("Config Root Format ERROR");
                    return false;
                }
                m_ResourceStartID = (uint)tmp;
            }

            // Auto resource id define from startID + 1
            AddResourceMapping(jsonPath, m_ResourceStartID);

            var abNames = parseResourceAbList(JObjRoot);

            // Add resource mapping
            JsonData JArrMembers = JObjRoot["res"]["members"];
            if (JArrMembers != null)
            {
                string url;
                EResourceConfigLevelSetting lowSetting, highSetting;
                string ab;
                string streamAB;
                string deps;
                ELoadAbOption isAB;
                MethodInfo modifier;
                //Parse resource mapping
                for (int i = 0; i < JArrMembers.Count; ++i)
                {
                    JsonData member = JArrMembers[i];
                    parseResourceTag(abNames, ResID, member, out url, out ab,out streamAB, out isAB, out deps, out lowSetting, out highSetting, out modifier);
                    var name = member["name"].ToString();
                    if (url == null)
                        url = name;
                    parseAndAddResourceMapping(ResID, name, url, ab, streamAB, isAB, lowSetting, highSetting, modifier,
#if UNITY_EDITOR
                        true
#else
                        false
#endif
                        , overwrite
                        );
                    if (isAB != ELoadAbOption.NotAb)
                        AddResconfDependenciesInNeed(ab, deps);
                }
            }

            //Parse preloadassets list
            JsonData JObjAtlas = JObjRoot["res"]["atlas"];
            if (JObjAtlas != null)
            {
                string url;
                EResourceConfigLevelSetting lowSetting, highSetting;
                string ab;
                string streamAB;
                string deps;
                ELoadAbOption isAB;
                MethodInfo modifier;
                for (int i = 0; i < JObjAtlas.Count; ++i)
                {
                    JsonData objAtlas = JObjAtlas[i];
                    JsonData objSprites = objAtlas["sprites"];
                    if (objSprites == null)
                    {
                        continue;
                    }
                    parseResourceTag(null, ResID, objAtlas, out url, out ab, out streamAB, out isAB, out deps, out lowSetting, out highSetting, out modifier);
                    for (int j = 0; j < objSprites.Count; ++j)
                    {
                        parseAndAddResourceMapping(ResID, objSprites[j].ToString(), url, ab, streamAB, isAB, lowSetting, highSetting, modifier, true, overwrite);
                    }
                    if (isAB != ELoadAbOption.NotAb)
                        AddResconfDependenciesInNeed(ab, deps);
                }
            }

            //Parse preloadassets list
            JsonData JObjpreloadassets = JObjRoot["preloadassets"];
            if (JObjpreloadassets != null)
            {
                for (int i = 0; i < JObjpreloadassets.Count; ++i)
                {
                    JsonData itemObj = JObjpreloadassets[i];
                    List<ResourceID> itemList = new List<ResourceID>();

                    string[] enumNames = ((string)itemObj["name"]).Split('|');
                    for (int j = 0; j < itemObj["resource"].Count; ++j)
                    {
                        JsonData itemStr = itemObj["resource"][j];
                        ResourceID IDdefined = 0;
                        if (m_ResourceAnoymousMapping.TryGetValue(itemStr.ToString(), out IDdefined) == false)
                        {
                            continue;
                        }

                        if (0 == IDdefined)
                        {
                            Debug.LogError("Undefined ResID Name: " + itemStr.ToString());
                            continue;
                        }
                        itemList.Add(IDdefined);
                    }

                    ResourceID[] itemArray = itemList.ToArray();
                    foreach (string enumName in enumNames)
                    {
                        Enum itemEnum = (Enum)Enum.Parse(Enum.IsDefined(EnumMode, enumName) ? EnumMode : EnumGame, enumName);
                        if (!m_PreloadResource.ContainsKey(itemEnum))
                        {
                            m_PreloadResource.Add(itemEnum, itemArray);
                        }
                        else
                        {
                            ResourceID[] originalArray = m_PreloadResource[itemEnum];
                            ResourceID[] newItemArray = new ResourceID[originalArray.Length + itemArray.Length];
                            Array.Copy(originalArray, newItemArray, originalArray.Length);
                            Array.Copy(itemArray, 0, newItemArray, originalArray.Length, itemArray.Length);
                            m_PreloadResource[itemEnum] = newItemArray;
                        }
                    }
                }
            }
            //MemoryProfiler.EndMonoProfile(profileTag);
            //if (GameVarDef.UnLoadNoCacheConfigText)
                Resources.UnloadAsset(jsonText);

            return true;
        }

        public bool ParseResourceConf(Type ResID, Type EnumGame, Type EnumMode)
        {
            if (m_HasParsed)
                return true;

            InitResFieldsDict(ResID);
            var ret = ParseResourceConfInternal(ResID, EnumGame, EnumMode, ResconfPath, false);
#if FULL_HD
            ret &= ParseResourceConfInternal(ResID, EnumGame, EnumMode, WwiseResconfPathHD, false);
            // HD config will overwrite the default one if same key was found.
            ret &= ParseResourceConfInternal(ResID, EnumGame, EnumMode, ResconfPathHD, true);
#endif

#if SPECTATOR_MODE
            // PC config will overwrite the default one if same key was found.
            ret &= ParseResourceConfInternal(ResID, EnumGame, EnumMode, ResconfPathPC, true);
#endif

            mResIDFieldInfos.Clear();
            mResIDFieldInfos = null;

            Debug.Log("Resource manager m_NameMapping size: " + m_NameMapping.Count);
            Debug.Log("Resource manager m_ResourcesMapping size:" + m_ResourceAnoymousMapping.Count + "/" + m_ResourcePaths.Count + "/" + m_ResourceMappingAB.Count);

            m_HasParsed = true;

            return ret;
        }

        public AsyncOperation ClearAllLoadedResource(bool includingPreloadResource = true)
        {
#if RESLOAD_TRACK
            string logFileName = string.Format("/RT.{0:yyyyMMddHHmmss}.log.csv", DateTime.Now);
            var outputStream = new StreamWriter(Application.persistentDataPath + logFileName, true);
            string logHeader = "ResPath,ByResLoad,ByABLoad,TimesFromID,TimesFromPath,TimesFromPool,TimeStartLoad,TimeEndLoad,Cost";
            string logFormat = "{0},{1},{2},{3},{4},{5},{6},{7},{8}";
            System.Text.StringBuilder strBuilder = new System.Text.StringBuilder();

            outputStream.WriteLine(logHeader);
            foreach (var kv in mDictResLoadStats)
            {
                var v = kv.Value;
                strBuilder.Length = 0;
                strBuilder.AppendFormat(logFormat, v.ResPath, v.FromRes, v.FromAB,
                    v.TimesFromID, v.TimesFromPath, v.TimesFromPool, 
                    v.TimeStartLoading, v.TimeEndLoading, v.Cost);
                outputStream.WriteLine(strBuilder.ToString());
            }
            outputStream.Flush();

            mDictResLoadStats.Clear();
#endif
            Debug.Log("ClearAllLoadedResource");
            ClearAllLoadedResourceInternal(includingPreloadResource);
            //CleanUpAsyncLoadResRequests();
            return Resources.UnloadUnusedAssets();
        }
        // now only work for not ab
        public void ClearResource(ResourceID id, bool isUnloadAsset = true)
        {
            if(m_ResourcePaths.ContainsKey(id))
            {
                ClearResource(m_ResourcePaths[id], isUnloadAsset);
            }
            else
            {
                ResourceConfigAB resConfig;
                if (m_ResourceMappingAB.TryGetValue(id, out resConfig))
                {
                    ClearResource(resConfig, isUnloadAsset);
                }
            }
            //PoolManager.CleanPool(id);
        }
        void ClearResource(ResourceConfigAB config, bool isUnloadAsset = true)
        {
            //var path = config.GetPath(ResourceQuality.Lowest);
            //if (path != null)
            //    ClearResource(path, isUnloadAsset);
            //path = config.GetPath(ResourceQuality.Low);
            //if (path != null)
            //    ClearResource(path, isUnloadAsset);
            //path = config.GetPath(ResourceQuality.High);
            //if (path != null)
            //    ClearResource(path, isUnloadAsset);
        }
        void ClearResource(string path, bool isUnloadAsset = true)
        {
            path = path.ToLower();
            if (m_ResourceBundle.ContainsKey(path))
            {
                m_ResourceBundle[path].Unload(true);
                m_ResourceBundle.Remove(path);
            }
            if (m_PreLoadedResourceBundles.ContainsKey(path))
            {
                m_PreLoadedResourceBundles.Remove(path);
            }
            if (m_LoadedResources.ContainsKey(path))
            {
                if (isUnloadAsset && !(m_LoadedResources[path] is GameObject) && !(m_LoadedResources[path] is Component && !(m_LoadedResources[path] is AssetBundle)))
                {
                    Resources.UnloadAsset(m_LoadedResources[path]);
                }
                //
                m_LoadedResources.Remove(path);
            }
            if (m_PreLoadedResources.ContainsKey(path))
            {
                m_PreLoadedResources.Remove(path);
            }
        }
        private void ClearAllLoadedResourceInternal(bool includingPreloadResource)
        {
            m_LoadedResources.Clear();
            //PoolManager.ClearAll();
            foreach (KeyValuePair<string, AssetBundle> pair in m_ResourceBundle)
            {
                if (includingPreloadResource == false && m_PreLoadedResourceBundles.ContainsKey(pair.Key))
                    continue;
                pair.Value.Unload(false);
            }
            m_ResourceBundle.Clear();

            if (includingPreloadResource)
            {
                m_PreLoadedResources.Clear();
                m_PreLoadedResourceBundles.Clear();
            }
            else
            {
                foreach (var pair in m_PreLoadedResources)
                {
                    m_LoadedResources.Add(pair.Key, pair.Value);
                }
                foreach (var pair in m_PreLoadedResourceBundles)
                {
                    m_ResourceBundle.Add(pair.Key, pair.Value);
                }
            }
        }

        public void UnloadBundleResourceByPath(string path)// not handle preload resource
        {
            AssetBundle ab;
            if (m_ResourceBundle.TryGetValue(path, out ab))
            {
                if (ab != null)
                {
                    ab.Unload(false);
                }
                m_ResourceBundle.Remove(path);
            }
        }

        public void GetPreloadResouce(ResourceID id, out bool is_preloaded, out UnityEngine.Object result)
        {
#if RESLOAD_TRACK
            string path = GetPathWithQuality(id);
            if (path != null)
            {
                var stats = GetResLoadStats(path);
                stats.TimesFromID++;
            }
#endif
            is_preloaded = false;
            result = null;
            if(!ResourceContains(id))
            {
                return;
            }

            //var resPath = GetPath(id, ResQualityLevel).ToLower();
            string resPath = null;
            if (resPath == null)
            {
                return;
            }
            if (m_PreLoadedResources.ContainsKey(resPath))
            {
                is_preloaded = true;
                result = m_PreLoadedResources[resPath];
                return;
            }

            ResourceConfigAB resConfig = m_ResourceMappingAB.ContainsKey(id) ? m_ResourceMappingAB[id] : null;

            var notUsingABForcefully = resConfig == null || !resConfig.IsAb;
            var abOption = resConfig == null ? ELoadAbOption.NotAb : resConfig.m_AbOption;
            var abPath = resConfig == null ? string.Empty : resConfig.m_abPath;
            var go = GetResource(resPath, abOption, abPath);
            if (go == null)
                return;

            m_PreLoadedResources[resPath] = go;

            if (m_UseAssetBundle && notUsingABForcefully == false)
            {
                if (string.IsNullOrEmpty(abPath))
                    abPath = resPath;
                AssetBundle ab;
                if (m_ResourceBundle.TryGetValue(abPath, out ab))
                {
                    m_PreLoadedResourceBundles[abPath] = ab;
                }
            }
            result = go;
            return;
        }

        public UnityEngine.Object GetResourceNoCache(ResourceID id)
        {
            return GetResource(id, noCache: true);
        }

        public UnityEngine.Object GetResourceByNameAndAb(string resPath, string abPath, bool preload, bool noCache)
        {
#if RESLOAD_TRACK
            var stats = GetResLoadStats(resPath);
            stats.TimesFromPath++;
#endif
            UnityEngine.Object result;
            if (preload && m_PreLoadedResources.TryGetValue(resPath, out result))
            {
                return result;
            }

            var abOption = string.IsNullOrEmpty(abPath) ? ELoadAbOption.NotAb : ELoadAbOption.AbWithPath;
            result = GetResource(resPath, abOption, abPath, noCache: noCache);
            
            if (result == null)
                return null;

            if (preload)
            {
                m_PreLoadedResources[resPath] = result;
                if (m_UseAssetBundle && abPath != null)
                {
                    AssetBundle ab;
                    if (m_ResourceBundle.TryGetValue(abPath, out ab))
                    {
                        m_PreLoadedResourceBundles[abPath] = ab;
                    }
                }
            }
            return result;
        }

        protected UnityEngine.Object GetResourceNoCache(string path, bool notUsingABForcefully = false)
        {
            return GetResource(path, notUsingABForcefully, noCache: true);
        }

        public ResourceID AddPlaceholderResForAb(string ab)
        {
            var resourceID = m_ResourceStartID++;

            m_ResourceMappingAB[resourceID] = new ResourceConfigAB()
            {
                m_path = "(placeholder for ab)",
                m_abPath = ab,
                m_AbOption = ELoadAbOption.AbWithNameOrPath,
            };

            return resourceID;
        }

        public void AddResconfDependenciesInNeed(string ab, string deps)
        {
            if (string.IsNullOrEmpty(ab) || string.IsNullOrEmpty(deps))
                return;

            m_ResconfDependencies.Remove(ab);
            var ss = deps.Split(',');
            for (var i = 0; i < ss.Length; i++)
            {
                var s = ss[i].Trim();
                if (string.IsNullOrEmpty(s))
                    continue;
                List<string> list;
                if (!m_ResconfDependencies.TryGetValue(ab, out list))
                {
                    list = m_ResconfDependencies[ab] = new List<string>();
                }
                list.Add(s);
            }
        }

        private bool LoadResconfDependenciesInNeed(string ab, bool recursive = false)
        {
            //check no dependencies
            List<string> list;
            if (m_ResconfDependencies.TryGetValue(ab, out list))
            {
                //load dependencies first
                foreach (var s in list)
                {
                    if (!LoadResconfDependenciesInNeed(s, true))
                    {
                        Debug.LogError("load ab dependency rec failed: " + ab + " " + s);
                        return false;
                    }
                }
            }
            
            //load self
            if (recursive && LoadABWithoutLoadingAssets(ab, false) == null)
            {
                Debug.LogError("load ab dependency failed: " + ab);
                return false;
            }
            return true;
        }

        public bool NeedDownloadOptionalBundle(bool assertInSingleBundle, params ResourceID[] ids)
        {
            if (ids == null || ids.Length == 0)
                return false;
            foreach (var id in ids)
            {
                if (id == ResourceID.INVALID || id == 0)
                    continue;
                if (NeedDownloadOptionalBundle(id))
                    return true;
                if (assertInSingleBundle)
                    return false;
            }
            return false;
        }
        public bool NeedDownloadOptionalBundle(ResourceID id)
        {
            if (id == ResourceID.INVALID || id == 0 /* in case not initialized*/)
                return false;
            ResourceConfigAB resConfig;
            if (!m_ResourceMappingAB.TryGetValue(id, out resConfig))
                return false;
            if (!resConfig.IsAb)
                return false;

            return false;


            //var ab = resConfig.m_abPath ;
            //bool result = NeedDownloadOptionalBundle(ab);
            //if(resConfig is ResourceConfigStreamAB)
            //{
            //    var stream_config = resConfig as ResourceConfigStreamAB;
            //    if(stream_config!= null)
            //    {
            //        result |= NeedDownloadOptionalBundle(stream_config.m_streamABPath);
            //    }
            //}
            //return result;
        }
        //public bool NeedDownloadOptionalBundle(string ab)
        //{
        //    if (string.IsNullOrEmpty(ab))
        //        return false;

        //    //check dependencies
        //    List<string> list;
        //    if (m_ResconfDependencies.TryGetValue(ab, out list))
        //    {
        //        foreach (var s in list)
        //        {
        //            if (NeedDownloadOptionalBundle(s))
        //                return true;
        //        }
        //    }

        //    var abReady = ResHotUpdater.instance.ABFileExist(ab);
        //    return !abReady;
        //}

        public List<string> GetResourceDependencies(string ab)
        {
            List<string> list;
            if (m_ResconfDependencies.TryGetValue(ab, out list))
            {
                return list;
            }
            return null;
        }

        public static GameObject Instant(UnityEngine.Object obj, Transform parent = null,
            bool setPosAndRot = false, Vector3 pos = default(Vector3), Quaternion rot = default(Quaternion))
        {
            //UnityEngine.Profiling.Profiler.BeginSample("ResourceManager.Instant");
            if (obj == null)
            {
                return null;
            }
            //Debug.LogError("obj.name " + obj.name);
#if DEVELOPMENT_BUILD
            UnityEngine.Profiling.Profiler.BeginSample("ResourceManager.Instant " + obj.name);
#endif
            GameObject go = null;
            if (parent != null)
            {
                if (setPosAndRot)
                {
                    go = (GameObject)GameObject.Instantiate(obj, pos, rot, parent);
                }
                else
                {
                    go = (GameObject)GameObject.Instantiate(obj, parent);
                }
            }
            else
            {
                if (setPosAndRot)
                {
                    go = (GameObject)GameObject.Instantiate(obj, pos, rot);
                }
                else
                {
                    go = (GameObject)GameObject.Instantiate(obj);
                }
            }
#if DEVELOPMENT_BUILD
            UnityEngine.Profiling.Profiler.EndSample();
#endif
            return go;
        }

        //public static GameObject InstantByResId(ResourceID resId, Transform parent = null,
        //    bool setPosAndRot = false, Vector3 pos = default(Vector3), Quaternion rot = default(Quaternion))
        //{
        //    if (resId == ResourceID.INVALID)
        //    {
        //        return null;
        //    }
        //    UnityEngine.Object obj = ResourceManager.instance.GetResource(resId);
        //    return Instant(obj, parent, setPosAndRot, pos, rot);
        //}
    }
}
